﻿using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using Tnelab.Common;

namespace Tnelab.Miniblink
{
    class WkeWebView : IWebView
    {
        static readonly object web_view_dic_lock=new object();
        static readonly Dictionary<IntPtr, WkeWebView> web_view_dic = new Dictionary<IntPtr, WkeWebView>();
        static MBWke.wkeJsNativeFunction JsNativeFunction;
        static long call_id_seed = 0;
        static readonly object js_call_native_event_arg_map_lock = new object();
        static readonly Dictionary<long, JsCallNativeEventArgs> js_call_native_event_arg_map = new Dictionary<long, JsCallNativeEventArgs>();        
        static WkeWebView()
        {
            JsNativeFunction = (es, p) =>
            {
                WkeWebView webView;
                lock (web_view_dic)
                {
                    webView = web_view_dic[MBWke.jsGetWebView(es)];
                }
                var messageId = MBWke.jsToInt(es, MBWke.jsArg(es, 0));
                var request = Marshal.PtrToStringUni(MBWke.jsToStringW(es, MBWke.jsArg(es, 1)));
                if (request == null)
                {
                    return MBWke.jsUndefined();
                }

                var callId = call_id_seed;
                call_id_seed++;
                JsCallNativeEventArgs args = new JsCallNativeEventArgs(webView, callId,messageId,request);
                var func = MBWke.jsArg(es, 2);
                var functionName = $"MbQueryCallback_{callId}";
                var ges = MBWke.wkeGlobalExec(webView.Handle);
                MBWke.jsSetGlobal(ges, functionName, func);
                args.Param = functionName;
                webView.OnJsCallNative(webView,args);
                return MBWke.jsUndefined();
            };
            MBWke.wkeJsBindFunction("mbQuery", JsNativeFunction, IntPtr.Zero, 3);
        }
        protected IntPtr ParentHandle { get; set; }
        public IntPtr Handle { get; private set; }

        public bool IsVip { get; private set; } = false;
        /// <summary>
        /// 获取webview大小
        /// </summary>
        public Size Size { get => new Size(MBWke.wkeGetWidth(Handle),MBWke.wkeGetHeight(Handle));}
        /// <summary>
        /// webview是否透明
        /// </summary>
        public bool Transparent
        {
            get
            {
                return MBWke.wkeIsTransparent(Handle);
            }
            set
            {
                MBWke.wkeSetTransparent(Handle, value);
            }
        }
        public bool LoadUrlEndEnable { get; set; } = true;

        public event EventHandler<TitleChangedEventArgs>? TitleChanged;
        public event EventHandler<LoadUrlBeginEventArgs>? LoadUrlBegin;
        public event EventHandler<DocumentReadyEventArgs>? DocumentReady;
        public event EventHandler<LoadUrlEndEventArgs>? LoadUrlEnd;
        public event EventHandler<PaintUpdatedEventArgs>? PaintUpdated;
        public event EventHandler<PaintBitUpdatedEventArgs>? PaintBitUpdated;
        public event EventHandler<JsCallNativeEventArgs>? JsCallNative;
        protected void OnTitleChanged(object sender,TitleChangedEventArgs args)
        {
            this.TitleChanged?.Invoke(sender, args);
        }
        protected void OnLoadUrlBegin(object sender,LoadUrlBeginEventArgs args)
        {
            this.LoadUrlBegin?.Invoke(sender, args);
        }
        protected void OnDocumentReady(object sender, DocumentReadyEventArgs args)
        {
            this.DocumentReady?.Invoke(sender, args);
        }
        protected void OnLoadUrlEnd(object sender, LoadUrlEndEventArgs args)
        {
            this.LoadUrlEnd?.Invoke(sender, args);
        }
        protected void OnPaintUpdated(object sender, PaintUpdatedEventArgs args)
        {
            this.PaintUpdated?.Invoke(sender, args);
        }
        protected void OnPaintBitUpdated(object sender, PaintBitUpdatedEventArgs args)
        {
            this.PaintBitUpdated?.Invoke(sender, args);
        }
        protected void OnJsCallNative(object sender, JsCallNativeEventArgs args)
        {
            lock (js_call_native_event_arg_map_lock)
            {
                js_call_native_event_arg_map.Add(args.CallId, args);
            }
            this.JsCallNative?.Invoke(sender, args);
        }
        #region 私有方法
        MBWke.wkePaintUpdatedCallback? PaintUpdatedCallback;
        void WkeOnPaintUpdated()
        {
            if (PaintUpdatedCallback == null)
            {
                PaintUpdatedCallback = (IntPtr webViewPtr, IntPtr param, IntPtr hdc, int x,int y, int width, int height) =>
                {
                    WkeWebView webView;
                    lock (web_view_dic)
                    {
                        webView = web_view_dic[webViewPtr];
                    }
                    var arg = new PaintUpdatedEventArgs(webView, hdc, x, y, width, height);
                    this.OnPaintUpdated(this, arg);
                };
            }
            MBWke.wkeOnPaintUpdated(Handle, PaintUpdatedCallback, IntPtr.Zero);
        }

        MBWke.wkePaintBitUpdatedCallback? PaintBitUpdatedCallback;
        void WkeOnPaintBitUpdated()
        {
            if (PaintBitUpdatedCallback == null)
            {
                PaintBitUpdatedCallback = (IntPtr webViewPtr, IntPtr param, IntPtr buffer,  ref MBWke.wkeRect mbRect, int width, int height) =>
                {
                    if (this.PaintBitUpdated != null)
                    {
                        unsafe
                        {
                            WkeWebView webView;
                            lock (web_view_dic)
                            {
                                webView = web_view_dic[webViewPtr];
                            }
                            var len = 4 * width * height;
                            var datas = new UnmanagedMemoryStream((byte*)buffer.ToPointer(), len);
                            var rect = new Rect();
                            rect.X = mbRect.x;
                            rect.Y = mbRect.y;
                            rect.Width = mbRect.w;
                            rect.Height = mbRect.h;
                            var args = new PaintBitUpdatedEventArgs(webView, datas, rect, width, height);
                            this.OnPaintBitUpdated(this, args);
                        }
                    }
                };
            }
            MBWke.wkeOnPaintBitUpdated(Handle, PaintBitUpdatedCallback, IntPtr.Zero);
        }
        MBWke.wkeTitleChangedCallback? TitleChangedCallback;
        void WkeOnTitleChanged()
        {
            if (TitleChangedCallback == null)
            {
                TitleChangedCallback = (webView, param, wkeString_Title) =>
                {
                    var titlePtr = MBWke.wkeGetString(wkeString_Title);
                    var title = Marshal.PtrToStringUTF8(titlePtr);
                    var args = new TitleChangedEventArgs(title==null?"":title);
                    this.OnTitleChanged(this, args);

                };
            }
            MBWke.wkeOnTitleChanged(Handle, TitleChangedCallback, IntPtr.Zero);
        }
        MBWke.wkeLoadUrlBeginCallback? LoadUrlBeginCallback;
        void WkeOnLoadUrlBegin()
        {
            if (LoadUrlBeginCallback == null)
            {
                LoadUrlBeginCallback = (webViewPtr, param, url, job) =>
                {
                    if (this.LoadUrlBegin != null)
                    {
                        WkeWebView webView;
                        lock (web_view_dic)
                        {
                            webView = web_view_dic[webViewPtr];
                        }
                        var args = new LoadUrlBeginEventArgs(webView,url);
                        this.OnLoadUrlBegin(this, args);
                        if (args.DataStream != null)
                        {
                            using var stream= args.DataStream;
                            var dataPtr = Marshal.AllocHGlobal((int)args.DataStream.Length);
                            var datas = new byte[stream.Length];
                            stream.Position = 0;
                            stream.Read(datas, 0, datas.Length);
                            Marshal.Copy(datas, 0, dataPtr, datas.Length);
                            MBWke.wkeNetSetData(job, dataPtr, datas.Length);
                            Marshal.FreeHGlobal(dataPtr);
                            args.DataStream = null;
                            return true;
                        }
                        else
                        {
                            if (this.LoadUrlEndEnable)
                            {
                                MBWke.wkeNetHookRequest(job);
                            }
                        }
                    }
                    return false;
                };
            }
            MBWke.wkeOnLoadUrlBegin(Handle, LoadUrlBeginCallback, IntPtr.Zero);
        }
        MBWke.wkeDocumentReady2Callback? DocumentReadyCallback;
        void WkeOnDocumentReadyCall()
        {
            if (DocumentReadyCallback == null)
            {
                DocumentReadyCallback = (webview, param, frameId) =>
                {
                    this.OnDocumentReady(this, new DocumentReadyEventArgs(frameId));
                };
            }
            MBWke.wkeOnDocumentReady2(Handle,DocumentReadyCallback,IntPtr.Zero);
        }
        MBWke.wkeLoadUrlEndCallback? LoadUrlEndCallback;
        void WkeOnLoadUrlEnd()
        {
            if (LoadUrlEndCallback == null)
            {
                LoadUrlEndCallback = (webViewPtr, param, url, job, buf, len) =>
                {
                    WkeWebView webView;
                    lock (web_view_dic)
                    {
                        webView = web_view_dic[webViewPtr];
                    }
                    var args = new LoadUrlEndEventArgs(webView, url, buf, len);
                    this.OnLoadUrlEnd(this, args);
                    if (args.DataStream != null)
                    {
                        using var stream = args.DataStream;
                        var datas=new byte[stream.Length];
                        stream.Position = 0;
                        stream.Read(datas, 0, datas.Length);
                        var dataPtr = Marshal.AllocCoTaskMem(datas.Length);
                        Marshal.Copy(datas, 0, dataPtr, datas.Length);
                        MBWke.wkeNetSetData(job, dataPtr, datas.Length);
                        Marshal.FreeCoTaskMem(dataPtr);
                        args.DataStream=null;
                    }
                };
            }
            MBWke.wkeOnLoadUrlEnd(Handle, LoadUrlEndCallback, IntPtr.Zero);
        }
        #endregion
        /// <summary>
        /// 构造WebView
        /// </summary>
        public WkeWebView(IntPtr parentHandle)
        {
            ParentHandle = parentHandle;
            if (parentHandle != IntPtr.Zero)
            {
                Win32Api.RECT rect;
                Win32Api.GetClientRect(parentHandle, out rect);
                var x = rect.left;
                var y = rect.top;
                var width = rect.right - rect.left;
                var height = rect.bottom - rect.top;
                Handle = MBWke.wkeCreateWebWindow(MBWke.wkeWindowType.WKE_WINDOW_TYPE_CONTROL, parentHandle, x, y, width, height);
                //MBVip.mbSetHandle(WebView, ParentHandle);
                //MBWke.wkeSetAutoDrawToHwnd(WebView, true);
            }
            else
            {
                Handle = MBWke.wkeCreateWebView();
            }
            lock (web_view_dic_lock)
            {
                web_view_dic[Handle] = this;
            }
            WkeOnTitleChanged();
            WkeOnPaintUpdated();
            WkeOnPaintBitUpdated();
            WkeOnLoadUrlBegin();
            WkeOnLoadUrlEnd();
        }
        public void Resize(int width,int height)
        {
            MBWke.wkeResize(Handle, width, height);
        }
        /// <summary>
        /// 加载URL
        /// </summary>
        /// <param name="url"></param>
        public void LoadUrl(string url)
        {
            MBWke.wkeLoadURL(Handle, url.ToString());
        }
        /// <summary>
        /// 触发键盘按下消息
        /// </summary>
        /// <param name="virtualKeyCode">虚拟键值</param>
        /// <param name="flags"></param>
        /// <param name="systemKey">是否系统键</param>
        /// <returns></returns>
        public bool FireKeyDownEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBWke.wkeFireKeyDownEvent(Handle, virtualKeyCode, flags, systemKey);
        }

        public bool FireKeyUpEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBWke.wkeFireKeyUpEvent(Handle, virtualKeyCode, flags, systemKey);
        }

        public void SetFocus()
        {
            MBWke.wkeSetFocus(Handle);
        }

        public void KillFocus()
        {
            MBWke.wkeKillFocus(Handle);
        }

        public bool FireKeyPressEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return MBWke.wkeFireKeyPressEvent(Handle, virtualKeyCode, flags, systemKey);
        }
        public bool FireWindowsMessage(uint message,uint wParam, uint lParam, out int result)
        {
            return MBWke.wkeFireWindowsMessage(Handle, this.ParentHandle, message, wParam, lParam, out result);
        }
        public bool FireMouseWheelEvent(int x, int y, int delta, uint flags)
        {
            return MBWke.wkeFireMouseWheelEvent(Handle, x, y, delta, flags);
        }

        public bool FireMouseEvent(uint message, int x, int y, uint flags)
        {
            return MBWke.wkeFireMouseEvent(Handle, message, x, y, flags);
        }

        public CursorType GetCursorType()
        {
            return (CursorType)MBWke.wkeGetCursorInfoType(Handle);
        }
        public string? RunJs(string script)
        {
            string? result = null;
            var jv = MBWke.wkeRunJSW(Handle, script);
            var es = MBWke.wkeGlobalExec(Handle);
            var val = MBWke.jsToStringW(es, jv);
            result = Marshal.PtrToStringUni(val);
            return result;
        }
        public Task<string?> RunJsAsync(string script)
        {
            throw new NotImplementedException();
        }
        public void Dispose()
        {            
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        bool is_disposed = false;
        protected void Dispose(bool disposing)
        {
            if (this.Handle == IntPtr.Zero)
                return;
            if (!is_disposed)
            {
                if (disposing)
                {
                    //Clean Up managed resources  
                }
                lock (web_view_dic_lock)
                {
                    web_view_dic.Remove(Handle);
                }
                lock (js_call_native_event_arg_map_lock)
                {
                    js_call_native_event_arg_map.Values.Where(it => it.WebView.Handle == this.Handle)
                        .ToList().ForEach(it=> js_call_native_event_arg_map.Remove(it.CallId));                    
                }
                MBWke.wkeDestroyWebView(this.Handle);
            }
            is_disposed = true;
        }

        public void ResponseJsCallNative(long callId, int messageId, string response)
        {
            JsCallNativeEventArgs args;
            lock (js_call_native_event_arg_map_lock)
            {
                args = js_call_native_event_arg_map[callId];
                js_call_native_event_arg_map.Remove(callId);
            }
            if (args == null)
                throw new InvalidOperationException("找不到对应的JsCallNativeEventArgs");
            var funcName = args.Param?.ToString();
            if (string.IsNullOrEmpty(funcName))
                throw new InvalidOperationException("JsCallNativeEventArgs.Param,此处为funcName,不能为空");
            MBWke.wkeRunJSW(args.WebView.Handle, $"{funcName}({args.MessageId},'{response}')");
            var es = MBWke.wkeGlobalExec(this.Handle);
            MBWke.jsDeleteObjectProp(es, MBWke.jsGetGlobal(es, "window"), funcName);
        }
        public void SetPosition(int x, int y)
        {
            MBWke.wkeSetHandleOffset(this.Handle, x, y);
        }

        public void SetDebugConfig(string debuString, string param)
        {
            if (debuString == null || param == null)
                return;
            MBWke.wkeSetDebugConfig(this.Handle, debuString, param);
        }
        ~WkeWebView()
        {
            Dispose(false);
        }
    }
}